package com.ikingtech.platform.service.system.organization.controller;

import com.ikingtech.framework.sdk.base.model.DragOrderParam;
import com.ikingtech.framework.sdk.context.event.TenantDeleteEvent;
import com.ikingtech.framework.sdk.context.event.TenantInitEvent;
import com.ikingtech.framework.sdk.context.exception.FrameworkException;
import com.ikingtech.framework.sdk.context.security.Me;
import com.ikingtech.framework.sdk.core.response.R;
import com.ikingtech.framework.sdk.data.mybatisplus.helper.DragHelper;
import com.ikingtech.framework.sdk.data.mybatisplus.helper.DragHelperBuilder;
import com.ikingtech.framework.sdk.enums.common.DragTargetPositionEnum;
import com.ikingtech.framework.sdk.enums.system.organization.OrganizationStatusEnum;
import com.ikingtech.framework.sdk.enums.system.organization.OrganizationStatusEnums;
import com.ikingtech.framework.sdk.label.api.LabelApi;
import com.ikingtech.framework.sdk.label.model.LabelAssignDTO;
import com.ikingtech.framework.sdk.log.embedded.annotation.OperationLog;
import com.ikingtech.framework.sdk.organization.api.OrgUserApi;
import com.ikingtech.framework.sdk.organization.api.OrganizationApi;
import com.ikingtech.framework.sdk.organization.model.OrganizationDTO;
import com.ikingtech.framework.sdk.organization.model.OrganizationQueryParamDTO;
import com.ikingtech.framework.sdk.organization.model.OrganizationUserDTO;
import com.ikingtech.framework.sdk.utils.Tools;
import com.ikingtech.framework.sdk.web.annotation.ApiController;
import com.ikingtech.platform.service.system.organization.entity.OrganizationDO;
import com.ikingtech.platform.service.system.organization.exception.OrganizationExceptionInfo;
import com.ikingtech.platform.service.system.organization.service.ModelConverter;
import com.ikingtech.platform.service.system.organization.service.OrganizationService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import static com.ikingtech.framework.sdk.context.constant.CommonConstants.ROOT_ORG_ID;

/**
 * @author tie yan
 */
@Slf4j
@RequiredArgsConstructor
@ApiController(value = "/system/organization", name = "系统管理-组织管理", description = "系统管理-组织管理，不支持多个根组织，所有组织均归属于默认根组织，默认跟组织编号为root")
public class OrganizationController implements OrganizationApi {

    private final OrganizationService service;

    private final OrgUserApi orgUserApi;

    private final LabelApi labelApi;

    private final ModelConverter converter;

    @Override
    @OperationLog(value = "新增组织", dataId = "#_res.getData()")
    @Transactional(rollbackFor = Exception.class)
    public R<String> add(OrganizationDTO organization) {
        OrganizationDO entity = Tools.Bean.copy(organization, OrganizationDO.class);
        entity.setId(Tools.Id.uuid());
        entity.setFullPath(this.service.parseFullPath(entity.getParentId(), entity.getId()));
        entity.setSortOrder(this.service.getMaxSortOrder(entity.getParentId()) + 1);
        entity.setTenantCode(Me.tenantCode());
        this.assignLabel(entity.getId(), organization.getLabelIds());
        this.service.save(entity);
        return R.ok(entity.getId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<Object> delete(String id) {
        OrganizationDO entity = this.service.getById(id);
        if (null == entity) {
            return R.ok();
        }
        if (Boolean.TRUE.equals(entity.getDefaultFlag())) {
            throw new FrameworkException(OrganizationExceptionInfo.DEFAULT_ORG_NOT_DELETE);
        }
        List<String> ids = this.service.listIdByFullPath(entity.getFullPath());
        if (Tools.Coll.isBlank(ids)) {
            return R.ok();
        }
        this.orgUserApi.moveUser(entity.getParentId(), ids);
        this.service.removeBatchByIds(ids);
        return R.ok();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<Object> update(OrganizationDTO organization) {
        if (!this.service.exist(organization.getId())) {
            throw new FrameworkException(OrganizationExceptionInfo.ORG_NOT_FOUND);
        }
        OrganizationDO entity = Tools.Bean.copy(organization, OrganizationDO.class);
        entity.setFullPath(this.service.parseFullPath(entity.getParentId(), entity.getId()));
        entity.setTenantCode(Me.tenantCode());
        this.assignLabel(entity.getId(), organization.getLabelIds());
        this.service.updateById(entity);
        return R.ok();
    }

    @Override
    public R<List<OrganizationDTO>> page(OrganizationQueryParamDTO queryParam) {
        List<String> dataScopeCodes = Me.dataScope();
        if (Me.invalidDataScope(dataScopeCodes)) {
            return R.ok(new ArrayList<>());
        }
        return R.ok(this.service.listPage(queryParam, dataScopeCodes, Me.tenantCode()).convertBatch(this.converter::modelConvert));
    }

    @Override
    public R<List<OrganizationDTO>> all() {
        return R.ok(this.converter.modelConvert(this.service.listByTenantCode(Me.tenantCode())));
    }

    @Override
    public R<OrganizationDTO> detail(String id) {
        OrganizationDO entity = this.service.getById(id);
        if (null == entity) {
            throw new FrameworkException(OrganizationExceptionInfo.ORG_NOT_FOUND);
        }
        List<String> subEntityIds = this.service.listIdsByFullPath(entity.getFullPath());
        return R.ok(this.converter.modelConvert(
                entity,
                this.orgUserApi.loadManager(entity.getManagerId()),
                this.orgUserApi.getUserCount(subEntityIds)
        ));
    }

    @Override
    public R<Object> drag(DragOrderParam dragParam) {
        DragHelper<OrganizationDO> dragHelper = DragHelperBuilder.builder(
                        () -> this.service.gt(dragParam.getParentId(), dragParam.getCurrentOrder()),
                        () -> this.service.gt(dragParam.getTargetParentId(), dragParam.getTargetOrder()),
                        (starOrder, endOrder) -> this.service.between(dragParam.getParentId(), starOrder, endOrder)
                )
                .which(dragParam.getParentId(), dragParam.getTargetParentId(), DragTargetPositionEnum.INNER.equals(dragParam.getPosition()))
                .currentNode(() -> this.service.getById(dragParam.getCurrentId()))
                .targetNode(() -> this.service.getById(dragParam.getTargetId()))
                .maxSortOrder(() -> this.service.getMaxSortOrder(dragParam.getTargetId()))
                .beforeTarget(DragTargetPositionEnum.BEFORE.equals(dragParam.getPosition()))
                .build();
        this.service.updateBatchById(dragHelper.drag());
        return R.ok();
    }

    @Override
    public R<Object> enable(String id) {
        OrganizationDO parentEntity = this.service.getById(id);
        List<String> orgIds = this.service.listIdByFullPath(parentEntity.getFullPath());
        List<OrganizationDO> entities = this.service.listByIds(orgIds);
        this.service.updateBatchById(Tools.Coll.traverse(entities, entity -> {
            entity.setStatus(OrganizationStatusEnums.ENABLE.name());
            return entity;
        }));
        return R.ok();
    }

    @Override
    public R<Object> disable(String id) {
        OrganizationDO parentEntity = this.service.getById(id);
        List<String> orgIds = this.service.listIdByFullPath(parentEntity.getFullPath());
        List<OrganizationDO> entities = this.service.listByIds(orgIds);
        this.service.updateBatchById(Tools.Coll.traverse(entities, entity -> {
            entity.setStatus(OrganizationStatusEnums.DISABLE.name());
            return entity;
        }));
        return R.ok();
    }

    @Override
    public R<List<OrganizationUserDTO>> pageUserById(OrganizationQueryParamDTO queryParam) {
        List<String> orgIds = this.service.listIdsByFullPath(queryParam.getId());
        return R.ok(this.orgUserApi.loadUserByDeptIds(queryParam.getPage(), queryParam.getRows(), orgIds));
    }

    @Override
    public R<List<OrganizationDTO>> listSubAll(OrganizationQueryParamDTO queryParam) {
        List<String> dataScope = Me.dataScope();
        if (Me.invalidDataScope(dataScope)) {
            return R.ok(new ArrayList<>());
        }
        queryParam.setOrgIds(dataScope);
        List<OrganizationDO> entities = this.service.listByCondition(queryParam, dataScope, Me.tenantCode());
        if (Tools.Coll.isBlank(entities)) {
            return R.ok(new ArrayList<>());
        }
        List<String> queriedOrgIds = Tools.Coll.convertList(entities, OrganizationDO::getId);
        List<String> queriedParentOrgIds = Tools.Coll.flatMap(
                entities,
                entity -> Tools.Coll.filter(
                        Tools.Str.split(
                                Tools.Str.isBlank(queryParam.getParentId()) ?
                                        entity.getFullPath() :
                                        entity.getFullPath().substring(entity.getFullPath().indexOf(queryParam.getParentId()))
                        ),
                        deptId -> !queriedOrgIds.contains(deptId)
                ),
                Collection::stream
        );
        if (Tools.Coll.isNotBlank(queriedParentOrgIds)) {
            queriedOrgIds.addAll(Tools.Coll.convertList(this.service.listByIds(queriedParentOrgIds), OrganizationDO::getId));
        }
        if (Tools.Str.isNotBlank(queryParam.getParentId())) {
            queriedOrgIds.addAll(this.service.listIdsByFullPaths(Tools.Coll.convertList(entities, OrganizationDO::getFullPath)));
        }

        return R.ok(this.converter.modelConvert(this.service.listByIds(Tools.Coll.distinct(queriedOrgIds))));
    }

    @EventListener
    public void initTenantOrgEventListener(TenantInitEvent event) {
        OrganizationDO entity = new OrganizationDO();
        entity.setId(Tools.Str.format("{}_{}", event.getCode(), ROOT_ORG_ID));
        entity.setName(event.getName());
        entity.setDefaultFlag(Boolean.TRUE);
        entity.setParentId(Tools.Str.EMPTY);
        entity.setTenantCode(event.getCode());
        entity.setGrade(1);
        entity.setStatus(OrganizationStatusEnum.ENABLE.name());
        entity.setFullPath(entity.getId());
        this.service.save(entity);
    }

    @EventListener
    public void deleteTenantOrgEventListener(TenantDeleteEvent event) {
        this.service.removeByTenantCode(event.getCode());
    }

    private void assignLabel(String orgId, List<String> labelIds) {
        LabelAssignDTO labelAssign = new LabelAssignDTO();
        labelAssign.setBusinessId(orgId);
        labelAssign.setLabelIds(labelIds);
        this.labelApi.assign(labelAssign);
    }
}
